﻿
using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Collections.Generic;

namespace GridStatLite
{
    sealed class UdpSender
    {
        // *************************************************************** //
        #region [ event declaration ]

        public event Action AllSentEvent;

        #endregion

        // *************************************************************** //
        #region [ member fields ]

        private readonly Socket m_socket;
        private readonly SocketAsyncEventArgs m_asyncEvtArgs;
        private readonly Action<SocketAsyncOperation, SocketError> ErrorCallback;

        private volatile IEnumerator<Tuple<byte[], int, int, IPEndPoint>> m_currentSending;

        #endregion

        // *************************************************************** //
        #region [ constructor ]

        /// <summary>
        /// the parameter "socket" and "SocketAsyncEventArgs" are delegated to this class
        /// so this class itself is not responsible for their lefe cycle management
        /// </summary>
        public UdpSender(Socket socket, SocketAsyncEventArgs evtArgs, Action<SocketAsyncOperation, SocketError> errorCallback)
        {
            m_socket = socket;

            m_asyncEvtArgs = evtArgs;
            m_asyncEvtArgs.Completed += this.OnSentCompleted;

            ErrorCallback = errorCallback;
        }

        #endregion

        // *************************************************************** //
        #region [ public APIs ]

        public void StartSend(IEnumerable<Tuple<byte[], int, int, IPEndPoint>> sendingDatas)
        {
            if (m_currentSending != null)
                throw new InvalidOperationException("previous sending NOT completed");

            m_currentSending = sendingDatas.GetEnumerator();
            SendNext();
        }

        #endregion

        // *************************************************************** //
        #region [ private helpers ]

        private void SendNext()
        {
            if (m_currentSending.MoveNext())
            {
                Tuple<byte[], int, int, IPEndPoint> tosend = m_currentSending.Current;

                m_asyncEvtArgs.SetBuffer(tosend.Item1, tosend.Item2, tosend.Item3);
                m_asyncEvtArgs.RemoteEndPoint = tosend.Item4;

                // +++++++++++++++++++++++++++++++ debug
                string dbgmsg = System.Text.Encoding.ASCII.GetString(tosend.Item1, tosend.Item2, tosend.Item3);
                Console.WriteLine("\t send:  {0}", dbgmsg);
                // +++++++++++++++++++++++++++++++ enddebug;

                if (!m_socket.SendToAsync(m_asyncEvtArgs))
                    OnSentCompleted(null, m_asyncEvtArgs);
            }
            else
            {
                m_currentSending = null;// speed up GC

                if (AllSentEvent != null)
                    AllSentEvent();
            }
        }

        private void OnSentCompleted(object sender, SocketAsyncEventArgs evtargs)
        {
            if (evtargs.LastOperation == SocketAsyncOperation.SendTo)
            {
                if (evtargs.SocketError == SocketError.Success)
                    SendNext();
                else
                    ErrorCallback(evtargs.LastOperation, evtargs.SocketError);
            }
        }

        #endregion
    }
}
